Skip to content

feat(REL-12754): Agent friendly error handling#661

Merged
nieblara merged 2 commits intoREL-12752-tty-1from
REL-12754-adding-better-errors
Apr 13, 2026
Merged

feat(REL-12754): Agent friendly error handling#661
nieblara merged 2 commits intoREL-12752-tty-1from
REL-12754-adding-better-errors

Conversation

@nieblara
Copy link
Copy Markdown
Contributor

@nieblara nieblara commented Mar 18, 2026

Make every error message actionable: preserve the HTTP status code, and suggest what to do next. Today, many errors lose their status code entirely and users see "unknown error occurred."

Three phases: (1) stop losing status codes by always producing valid JSON errors, (2) add a suggestion registry mapping status codes to guidance, (3) update plaintext error formatting to include suggestions.


Note

Medium Risk
Touches core request/response formatting used across most CLI commands, so regressions could change user-visible output or automation expectations. Changes are mostly additive (status codes/suggestions, optional --fields, and improved plaintext formatting) with extensive test coverage.

Overview
Makes API errors agent-friendly. HTTP error responses now always become valid JSON that includes statusCode (and, for common statuses like 401/403/404/409/429, an actionable suggestion), and plaintext error output prints the suggestion when present.

Adds output shaping and better plaintext rendering. Introduces global --fields to filter top-level keys in JSON output, and enhances plaintext output to render registered resources (e.g. flags, members, projects, environments, segments) as tables for lists and key-value blocks for singular results; commands now pass ResourceName into CmdOutput to enable this.

Reviewed by Cursor Bugbot for commit 6e02700. Bugbot is set up for automated code reviews on this repo. Configure here.


Related Jira issue: REL-12754: Better error messages with actionable suggestions

@launchdarkly-upra launchdarkly-upra bot changed the title [feat] Agent friendly error handling [REL-12754] [feat] Agent friendly error handling Mar 18, 2026
errMap["suggestion"] = suggestion
}
resp, _ := json.Marshal(errMap)
return body, errors.NewError(string(resp))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty-body error path returns empty bytes instead of synthesized JSON

Low Severity

When the HTTP response has no body, the function synthesizes a proper JSON error into resp but returns the original empty body as the first return value. In contrast, the non-empty body path reassigns body to the enriched JSON before returning it. This means a caller inspecting the first return value gets enriched JSON in one case and empty bytes in the other. The fix would be to return resp instead of body on that line.

Additional Locations (1)
Fix in Cursor Fix in Web

} else {
if _, exists := errMap["statusCode"]; !exists {
errMap["statusCode"] = res.StatusCode
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nil map panic when server returns JSON null body

Medium Severity

If the server returns the literal JSON value null as the error body, json.Unmarshal succeeds (returns nil error) but sets errMap to nil. The code then enters the else branch and attempts to write to the nil map at errMap["statusCode"] = res.StatusCode, which causes a runtime panic. Reading a nil map is safe in Go, but writing to one is not.

Additional Locations (1)
Fix in Cursor Fix in Web

@nieblara nieblara changed the title [REL-12754] [feat] Agent friendly error handling feat(REL-12754): Agent friendly error handling Mar 18, 2026
Copy link
Copy Markdown

@JackDanger JackDanger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great improvement — actionable errors are one of the highest-leverage changes for both agents and humans. The suggestion registry is a smart pattern. Some things to consider:

1. Error JSON shape change is a breaking contract for agent-prompts consumers
The launchdarkly-labs/agent-prompts repo (the official LD agent guide) teaches agents to check errors like this:

echo "$RESULT" | jq -e '.code == 401' > /dev/null

Before this PR, the code field could be a numeric-ish string or omitted. After this PR, it's always a snake_case string like "unauthorized", and the numeric value moves to statusCode. This will break that exact pattern. The agent-prompts repo needs to be updated alongside this PR, or at minimum the PR description should call out the shape change explicitly.

2. baseURI extraction from the request path is fragile

if parsed, err := url.Parse(path); err == nil && parsed.Scheme != "" {
    baseURI = parsed.Scheme + "://" + parsed.Host
}

If path is ever a relative path (e.g., /api/v2/flags/my-proj), baseURI will be empty, and the 401 suggestion will contain a broken link: /settings/authorization with no host. Worth adding a fallback to the configured --base-uri value, or at minimum a test that exercises the relative-path case.

3. The suggestion for 404 could mislead

"Use ldcli projects list or ldcli flags list to see available resources."

If I'm working with segments, environments, metrics, or any of the ~375 auto-generated resource commands, this suggestion is wrong. Could the suggestion include the resource type from the command context? e.g., "Use ldcli segments list" when the failing command was ldcli segments get.

4. Missing suggestion for 400 (Bad Request)
This is arguably the most common error in practice — malformed JSON payloads, invalid field values, missing required fields. The API often returns helpful message text for 400s, but a suggestion like "Check the --data payload format. Use ldcli <resource> create --help for required fields." would help agents self-correct.

5. Non-JSON error bodies get the raw HTML/text stuffed into message

"message": string(body),  // Could be "<html><body>Bad Gateway</body></html>"

This is tested and intentional, which is fine. But for agents, an HTML blob in the message field burns tokens. Consider truncating or sanitizing the message when the body isn't JSON (e.g., first 200 chars + ...).

6. Cursor Bugbot flagged 2 issues — worth checking what those were before merging.

* [feat] adding --fields flag

* feat(REL-15756): updating agent friendly and improved rich text output (#663)

feat(REL-15756) updating agent friendly and improved rich text output
@nieblara nieblara merged commit b0d06c4 into REL-12752-tty-1 Apr 13, 2026
13 checks passed
@nieblara nieblara deleted the REL-12754-adding-better-errors branch April 13, 2026 23:54
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 6e02700. Configure here.

}
if suggestion := errors.SuggestionForStatus(res.StatusCode, baseURI); suggestion != "" {
errMap["suggestion"] = suggestion
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion unconditionally overwrites API-provided value unlike statusCode

Medium Severity

The statusCode field is guarded with an existence check (if _, exists := errMap["statusCode"]; !exists) before being set, but the suggestion field is unconditionally overwritten. If the API response already contains a suggestion field (which would be more specific than the generic client-side one), it gets silently replaced. The same existence-check pattern used for statusCode is missing for suggestion.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6e02700. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants